package edu.caece.langprocessor.lexical;

import static java.lang.Character.toLowerCase;

import java.io.IOException;
import java.io.Reader;
import java.util.Set;
import java.util.regex.Pattern;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.annotation.Autowired;

import edu.caece.automaton.Automaton;
import edu.caece.langprocessor.lexical.tokens.ErrorLexicalToken;
import edu.caece.langprocessor.lexical.tokens.LexicalToken;
import edu.caece.langprocessor.lexical.tokens.SimpleLexicalToken;
import edu.caece.langprocessor.lexical.tokens.TokenType;

public class LexicalProcessor {

	@Autowired
	private Automaton automaton;
	private Set<String> spaceCharacters;
	private Pattern spaceCharPattern;
	private Character remainderChar = null;
	private static final Log LOGGER = LogFactory.getLog(LexicalProcessor.class);
	private String commentStartCharacter;
	private Pattern commentStartCharacterPattern;
	private String commentEndCharacter;
	private Pattern commentEndCharacterPattern;
	private PositionalFileReaderDecorator reader;
	private boolean endOfFileReached;
	private Set<String> vocabulary;
	private final Set<String> accepStateToIdentifier;

	public LexicalProcessor(Set<String> vocabulary, Set<String> accepStateToIdentifier, 
			Set<String> spaceCharacters, String commentStartCharacter, String commentEndCharacter) {
		
		this.vocabulary = vocabulary;
		this.accepStateToIdentifier = accepStateToIdentifier;
		
		this.spaceCharacters = spaceCharacters;
		this.spaceCharPattern = this.getCharPattern(this.spaceCharacters);

		this.commentStartCharacter = commentStartCharacter;
		this.commentStartCharacterPattern = Pattern.compile(this.commentStartCharacter);

		this.commentEndCharacter = commentEndCharacter;
		this.commentEndCharacterPattern = Pattern.compile(this.commentEndCharacter);
		endOfFileReached = false;
	}

	public LexicalToken getNextLexicalToken() {

		LexicalToken lexicalToken = null;
		StringBuilder lexema = new StringBuilder();
		int readValue;
		Character character;
		Boolean isAcceptState = false;
		String stateName = "error";
		boolean processNextChar = true;

		int line;
		int column;

		if (!endOfFileReached) {
			try {
				readValue = readFirstValue();

				if (readValue != -1) {
					character = toLowerCase((char) readValue);
					// Se ignoran los espacios y los comentarios
					while ((this.spaceCharPattern.matcher(character.toString())
							.matches() || this.commentStartCharacterPattern
							.matcher(character.toString()).matches())
							&& readValue != -1) {
						// ignoro los caracteres que respresentan espacios
						readValue = this.skipSpaces(this.spaceCharPattern,
								readValue);
						// ignoro los comentarios
						readValue = this.skipComments(
								this.commentStartCharacterPattern,
								this.commentEndCharacterPattern, readValue);
						character = toLowerCase((char) readValue);
					}
				}
				// se define fila y columna para el elemento a obtener
				line = this.reader.getLine();
				column = this.reader.getColumn();
				// si aun quedan caracteres por procesar
				if (readValue != -1) {
					character = Character.toLowerCase((char) readValue);
					// proceso el token
					this.automaton.reset();
					while ((processNextChar) && (readValue != -1)) {
						lexema.append(character);
						isAcceptState = this.automaton.step(character);
						readValue = this.reader.read();
						character = Character.toLowerCase((char) readValue);
						processNextChar = this.automaton.getAcceptStates()
								.contains(this.automaton.peek(character));
					}
					// si no se paro por fin de archivo guardamos el caracter
					if (readValue != -1)
						this.remainderChar = character;
					stateName = this.automaton.getCurrentState().getName();
					if (LOGGER.isDebugEnabled()) {
						LOGGER.debug("Accept State: "
								+ isAcceptState.toString() + " State : "
								+ this.automaton.getCurrentState().toString());
					}
				} else {
					this.endOfFileReached = true;
					lexema.append("$");
					stateName = "endOfTree";
				}
				
				String token = stateName;
				// Se define si el estado final corresponde a un identifier 
				if (this.accepStateToIdentifier.contains(stateName))
					 token = TokenType.identifier.getValue();  
				// Se setea el token correspondiente
				if (this.vocabulary.contains(token)) {
					lexicalToken = new SimpleLexicalToken(TokenType.StringToTokenType(token), lexema.toString());
				} else {
					lexicalToken = new ErrorLexicalToken(lexema.toString());
				}
				lexicalToken.setLine(line);
				lexicalToken.setColumn(column);

			} catch (IOException e) {
				e.printStackTrace();
			}
		}
		return lexicalToken;
	}

	private int readFirstValue() throws IOException {
		int readValue;
		// tengo un caracter de una ejecucion anterior
		if (this.remainderChar != null) {
			readValue = (int) this.remainderChar;
			this.remainderChar = null;
		} else {
			readValue = this.reader.read();
		}
		return readValue;
	}

	private int skipSpaces(Pattern p, Integer readValue) throws IOException {

		int retValue = readValue;
		Character character;
		character = Character.toLowerCase((char) retValue);

		while (p.matcher(character.toString()).matches() && (readValue != -1)) {
			retValue = this.reader.read();
			character = Character.toLowerCase((char) retValue);
		}
		return retValue;
	}

	private int skipComments(Pattern startChar, Pattern endChar,
			Integer readValue) throws IOException {

		int retValue = readValue;
		Character character;
		character = Character.toLowerCase((char) retValue);

		if (startChar.matcher(character.toString()).matches()
				&& (retValue != -1)) {
			retValue = this.reader.read();
			character = Character.toLowerCase((char) retValue);
			while (!this.commentEndCharacterPattern.matcher(
					character.toString()).matches()
					&& (readValue != -1)) {
				retValue = this.reader.read();
				character = Character.toLowerCase((char) retValue);
			}
			if (retValue != -1) {
				retValue = this.reader.read();
			}
		}
		return retValue;
	}

	public void setCodeReader(Reader reader) {
		this.reader = new PositionalFileReaderDecorator(reader);
	}

	private Pattern getCharPattern(Set<String> set) {
		String pattern = "";
		for (String s : set) {
			pattern = pattern.equals("") ? s : (pattern + "|" + s);
		}
		return Pattern.compile(pattern);
	}

}
